import {
  Box3,
  Object3D,
  OrthographicCamera,
  PerspectiveCamera,
  Sphere,
  Vector,
  Vector2,
  Vector3,
  Vector3Tuple,
} from 'three-legacy';

import {
  cmdInvoker,
  CMEVENTS,
  CSLine,
  CSPoint,
  CurveStyle,
  ExtensionID,
  ICurve,
  ISketcherData,
  MathTool,
  pickedFaceData,
  SceneGraph,
  sketch_grids,
  SketchFrame,
  Tuple3,
  withFeature,
} from '@amcax/base';
import {
  getObjectInfo,
  Picker2D,
  Picker3D,
  PickerMgr,
  Renderer2D,
  Renderer3D,
  RendererMgr,
} from '@amcax/renderer';
import { DimInfo } from '@app-base/environments/nextcad/commands/sketch/cmd-render-pick/type';
import { AnnotationType } from '@app-base/environments/nextcad/commands/sketch/dimension/types';
import { MessageTip } from '@app-cad/common/utils/MessageTip';
import { ReturnDataInfo } from '@app-cad/environments/nextcad/commands/CmdBase';
import { dimPicker } from '@app-base/environments/nextcad/commands/sketch/cmd-render-pick/dimPick';
import { dimRender } from '@app-base/environments/nextcad/commands/sketch/cmd-render-pick/render';
import { sketchEventCenter } from '@app-cad/environments/nextcad/events/SketchEventCenter';
import { cmdMgr } from '@app-cad/manager/CmdManager';
import {
  CoreBasicCommands,
  ctx2,
  docMgr,
  previewer,
  userEvents,
} from '@app-cad/module';
import { wsApi } from '@app-cad/server';
import { SKETCH_EVENT } from '../../Constants';
import { defaultEventCenter } from '../../events/DefaultEventCenter';
import { eventCenterManager } from '../../events/EventCenterManager';
import { getNearFaceNormalAndUp } from '../camera';
import { ConstraintIcon } from '../renderObjects.ts/ConstraintIcon';
import { constraintIconPicker } from './ConstraintIconPicker';
import { constraintIconRenderer } from './ConstraintIconRenderer';
import { sketchStore } from './useSketchStore';

export const CONSTRUCTION_LINE = 'construction-line';
export const RETAIN = 3;

/**
 * @description 根据向量、长度、点计算另一个点
 * @param vector 向量
 * @param length 长度
 * @param point 点
 * */
export function calculatePoint(
  vector: Vector3,
  length: number,
  point: Vector3,
): Vector3 {
  // 假设P是一个Three.js Vector3对象，表示点P
  // length是你想要的线段PQ的长度
  // 创建一个新的向量V，它是任意方向的（例如，从P到Q的方向）
  // 规范化向量V，使其长度为1
  vector.normalize();
  // 计算新的点Q，它是点P在向量V方向上长度为length的投影
  return point.clone().add(vector.clone().setLength(length));
}

/**
 * @description 获取两点之间的角度 与x轴的夹角
 * @param endPoint  结束点
 * @param startPoint  起始点
 * @return 角度 number
 * */
export function getAngle(
  endPoint: Vector3,
  startPoint: Vector3,
  vec: Vector3 = new Vector3(1, 0, 0),
): number {
  const vector = startPoint.clone().sub(endPoint);
  let angle = vector.angleTo(vec);
  if (startPoint.y < endPoint.y) {
    angle = Math.PI - angle;
  } else {
    angle = Math.PI + angle;
  }
  return angle;
}

/**
 * @description 根据向量获取两个点的点积 （判断正负）
 * @param startPoint 起始点
 * @param endPoint 结束点 (即鼠标正在移动的点)
 * @param vector 向量 （即方向）
 * @return 点积结果 1 即过点startPoint 在startPoint的上方，-1 即过点startPoint 在startPoint的下方
 * */
export function getDotProduct(
  startPoint: Vector3,
  endPoint: Vector3,
  vector: Vector3,
): number {
  return vector.clone().normalize().dot(endPoint.clone().sub(startPoint));
}

/**
 * @description 获取中心点
 * @param point1 点1
 * @param point2 点2
 * */
export function getCenterPoint(point1: Vector3, point2: Vector3): Vector3 {
  const midpoint = new Vector3();
  midpoint.addVectors(point1, point2).divideScalar(2);
  return midpoint;
}

/**
 * @description 点到直线的投影点
 * @param point 点 (即鼠标位置)
 * @param lineStart 直线起点
 * @param lineVec 直线向量
 * */
export function pointProjectOnLine<T extends Vector>(
  point: T,
  lineStart: T,
  lineVec: T,
) {
  lineVec = lineVec.clone().normalize();
  const v = point.clone().sub(lineStart);
  const distance = v.dot(lineVec);
  return lineStart.clone().add(lineVec.multiplyScalar(distance));
}

/**
 * @description 计算点与多边形最近的两个点
 * @param point 点 (即鼠标的位置)
 * @param vertices 多边形的顶点
 * */
export function getLastDistPoint(
  point: Vector3,
  vertices: Vector3[],
): [Vector3, Vector3] {
  const distanceList = [];
  for (let i = 0; i < vertices.length; i++) {
    distanceList.push(vertices[i].distanceTo(point));
  }
  const min = Math.min(...distanceList);
  const index = distanceList.indexOf(min);
  let nextIndex = null;
  if (distanceList.length === index + 1) {
    nextIndex = distanceList[index - 1] > distanceList[0] ? 0 : index - 1;
  } else if (index === 0) {
    nextIndex =
      distanceList[1] > distanceList[distanceList.length - 1]
        ? distanceList.length - 1
        : 1;
  } else {
    nextIndex =
      distanceList[index - 1] > distanceList[index + 1] ? index + 1 : index - 1;
  }
  return [vertices[index], vertices[nextIndex]];
}

/**
 * @param startPoint 开始点
 * @param endPoint 结束点
 * @param angle 角度
 * @param lineVector 直线向量
 * @lineLength 长度
 * @returns 计算后的点 即约束在规定角度上的点
 * */
export function calculateNewPoint(
  startPoint: Vector3,
  endPoint: Vector3,
  angle: number,
  lineVector: Vector3,
  lineLength?: number,
) {
  const distance = lineLength ? lineLength : startPoint.distanceTo(endPoint);
  // 判断开始点是否等于结束点
  if (startPoint.equals(endPoint)) {
    return endPoint;
  } else {
    const radian = MathTool.degToRad(angle);
    const isSymbol =
      getDotProduct(startPoint, endPoint, lineVector) >= 0 ? 1 : -1;
    const newPoint = new Vector3(
      startPoint.x + distance * Math.cos(radian) * isSymbol,
      startPoint.y + distance * Math.sin(radian) * isSymbol,
      0,
    );
    return newPoint;
  }
}

export const axis = ['X-AXIS', 'Y-AXIS'];
export const parseAxis = (obj: ICurve): ICurve => {
  if (axis.includes(obj.id)) {
    return {
      ...obj,
      vertices:
        obj.id === 'X-AXIS'
          ? [
              [0, 0],
              [1, 0],
            ]
          : [
              [0, 0],
              [0, 1],
            ],
    };
  }

  if (obj.id === 'ORIGIN') {
    return {
      ...obj,
      visible: true,
    };
  }

  return obj;
};

/**
 * @description 重新渲染
 * @param sketchId 草图id
 * @param data 草图数据
 * */
export const hiddenPoint = new Set<string>();

function setHiddenPoint(curves: ICurve[]) {
  const hiddenP = curves.filter(
    (item) => !item.visible && !axis.includes(item.id),
  );
  hiddenP.forEach((item) => hiddenPoint.add(item.id));
  const showP = curves.filter((item) => item.visible);
  removeHiddenPoint(showP.map((item) => item.id));
}

function removeHiddenPoint(curveIds: string[]) {
  curveIds.forEach((item) => hiddenPoint.delete(item));
}

export async function renderSketch(sketchId: string, data: ReturnDataInfo) {
  const renderer: Renderer2D = RendererMgr.getRender<Renderer2D>('2d');
  try {
    let changedData: ISketcherData = null;

    const updates = [...(data.added ?? []), ...(data.changed ?? [])].filter(
      (id) => basicSketchIds.every((s) => !id.startsWith(s)),
    );
    // 获取数据
    if (data.removed && data.removed.length > 0) {
      removeHiddenPoint(data.removed);
      sketchStore.getState().deleteSketchCurves(data.removed);
      renderer.deleteModels(data.removed);
      // guideRenderer().deletePoint(data.removed);
      dimRender().remove(data.removed);
      constraintIconRenderer.removeICurves(data.removed);
    }
    if (updates?.length > 0) {
      changedData = await wsApi.getSketchCurve(sketchId, updates);
      setHiddenPoint(changedData.curves);
      const changedCurves = changedData?.curves.map(parseAxis);
      const curves = sketchStore.getState().curves;

      const curveIdSet = new Set(curves.map((curve) => curve.id));

      const addedCurves = changedCurves.filter((c) => !curveIdSet.has(c.id));
      const updatedCurves = changedCurves.filter((c) => curveIdSet.has(c.id));

      constraintIconRenderer.removeICurves(updates);
      sketchStore
        .getState()
        .updateSketchCurves([
          ...curves.filter(
            (c) => !changedCurves.some((newCurve) => newCurve.id === c.id),
          ),
          ...changedCurves,
        ]);
      // renderer2d的update存在问题，无法正常修改已存在的curve的visible，需要排查
      renderer.deleteModels(updatedCurves.map((c) => c.id));
      renderer.drawNewModels([changedData]);
      // guideRenderer().setGuidePoint(changedData.curves);
      dimRender().update(updates, changedData.curves);
      constraintIconRenderer.addICurves(addedCurves);
      constraintIconRenderer.updateICurves(updatedCurves);
      return {
        addData: { ...changedData, curves: addedCurves } as ISketcherData,
        changedData: {
          ...changedData,
          curves: updatedCurves,
        } as ISketcherData,
      };
    }
  } catch (error) {
    console.error(error);
    return null;
  }
}

// 重新渲染标注
export const resetRenderDim = async (sketchId: string) => {
  dimRender()?.removeByDimIds('all');
  const dimData = await wsApi.getSketchAnnotations(sketchId);
  dimRender().renderDims(dimData.annotations);
};
// 新加的标注 -> 会自动对比标注id 如果没有就新增，如果有就不变且不会取更新
export const updataRenderDim = async (sketchId: string) => {
  const dimData = await wsApi.getSketchAnnotations(sketchId);
  dimRender().renderDims(dimData.annotations);
};

/**
 * @description  重新渲染草图
 */
export const resetRenderSketch = async (sketchId: string) => {
  try {
    const renderer = RendererMgr.getRender<Renderer2D>('2d');

    renderer?.clear();
    dimRender()?.remove();
    constraintIconRenderer.clear();
    // guideRenderer()?.destroy();
    const res = await wsApi.getSketchCurves(sketchId, true);
    // 无需await
    cmdMgr.updateUndoSize();
    const rawCurves = res.curves;
    res.curves = res?.curves?.map(parseAxis);
    sketchStore.getState().updateSketchCurves(res.curves);
    renderer.render([res]);
    // guideRenderer().createdGuide([res]);

    // in case sketcher exited immediately after entered, the sketch is deleted automatically
    // and this will throw error
    const dimData = await wsApi.getSketchAnnotations(sketchId);

    dimRender().setRenderFunc(dimData.annotations, res?.curves);
    dimRender().setLabelBlurFun(async (params) => {
      const res = await wsApi.updateDimension(sketchId, params);
      await renderSketch(sketchId, res);
    });

    constraintIconRenderer.addICurves(rawCurves);
    setHiddenPoint(res.curves);
    return res;
  } catch {
    return null;
  }
};
/**
 * @description 根据key删除对象中的属性
 * @param data 数据
 * @param keys 需要删除的键
 * */
export const deleteByKeys = (data, keys: string[], like = false) => {
  if (like) {
    keys.forEach((key) => {
      Object.keys(data).forEach((item) => {
        if (item.includes(key)) {
          delete data[item];
        }
      });
    });
  } else {
    keys.forEach((key) => {
      delete data[key];
    });
  }
  return data;
};

// 计算点到直线的距离的辅助函数
/**
 * @param point 点
 * @param {start: 点(向量经过的点), end: 向量, normal: end的垂直向量} line 直线
 * @returns 距离
 * */
export function distanceToLine<T extends Vector>(
  point: T,
  line: { start: T; vec: T; normal: T },
) {
  const dir = line.vec;
  const diff = point.clone().sub(line.start);
  return Math.abs(line.normal.dot(diff)) / dir.length(); // 点积除以线长
}

export type Coordinates = {
  x: number;
  y: number;
  isParallel?: boolean;
  hasCross?: boolean;
  isCommonPoint?: boolean;
};
const formatPoint = ([p0, p1, p2, p3]) => {
  const s1_x = p1.x - p0.x;
  const s1_y = p1.y - p0.y;
  const s2_x = p3.x - p2.x;
  const s2_y = p3.y - p2.y;
  const vec1 = new Vector2(s1_x, s1_y).normalize();
  const vec2 = new Vector2(s2_x, s2_y).normalize();
  return { s1_x, s1_y, s2_x, s2_y, vec1, vec2 };
};

/**
 * @description 根据四个点计算交点 （这个两线段不能平行）
 * @param p0 第一个线段的起点
 * @param p1 第一个线段的终点
 * @param p2 第二个线段的起点
 * @param p3 第二个线段的终点
 * @returns {{x: number, y: number}} 交点
 * */
export function getLineIntersection(
  p0: Coordinates,
  p1: Coordinates,
  p2: Coordinates,
  p3: Coordinates,
  region?: number,
): Coordinates {
  const { s1_x, s1_y, s2_x, s2_y, vec1, vec2 } = formatPoint([p0, p1, p2, p3]);
  const dotProduct = vec1.dot(vec2);
  if (Math.abs(dotProduct - 1) < Number.EPSILON) {
    p1.isParallel = true;
    return p1;
  } else {
    const s =
      (-s1_y * (p0.x - p2.x) + s1_x * (p0.y - p2.y)) /
      (-s2_x * s1_y + s1_x * s2_y);
    const t =
      (s2_x * (p0.y - p2.y) - s2_y * (p0.x - p2.x)) /
      (-s2_x * s1_y + s1_x * s2_y);
    // 是否有交点 （排除延长线上的交点）
    const hasCross = s >= 0 && s <= 1 && t >= 0 && t <= 1;
    if (isNaN(t)) {
      return p1;
    } else {
      const x = p0.x + t * s1_x;
      const y = p0.y + t * s1_y;
      if (
        region &&
        new Vector2(x, y).distanceTo(new Vector2(p1.x, p1.y)) > region * 3
      ) {
        p1.isParallel = true;
        return p1;
      }
      return { x, y, hasCross };
    }
  }
}

export function getLineIntersectionTwo(p0, p1, p2, p3) {
  const { s1_x, s1_y, s2_x, s2_y, vec1, vec2 } = formatPoint([p0, p1, p2, p3]);
  let p = null;
  // 交点
  if (isEquals(p0, p2)) {
    p = p0;
  } else if (isEquals(p0, p3)) {
    p = p0;
  } else if (isEquals(p1, p2)) {
    p = p1;
  } else if (isEquals(p1, p3)) {
    p = p1;
  }
  if (isParallel(vec2, vec1)) {
    return {
      ...p,
      isParallel: true,
      isCommonPoint: !!p,
    };
  } else {
    if (p) {
      p.isParallel = false;
      p.isCommonPoint = true;
      return p;
    } else {
      const t =
        (s2_x * (p0.y - p2.y) - s2_y * (p0.x - p2.x)) /
        (-s2_x * s1_y + s1_x * s2_y);
      const x = p0.x + t * s1_x;
      const y = p0.y + t * s1_y;
      return { x, y, isParallel: false, isCommonPoint: false };
    }
  }
}

const minNum = 1e-5;

export const isEquals = (p1, p2) => {
  if (!p1 || !p2) {
    return false;
  }
  return Math.abs(p1.x - p2.x) < minNum && Math.abs(p1.y - p2.y) < minNum;
};

/**
 * @description 计算对称点
 * @param point 点
 * @param center 对称中心
 * */
export function getSymmetricPoint(point: Vector3, center: Vector3): Vector3 {
  return new Vector3(
    center.x + (center.x - point.x),
    center.y + (center.y - point.y),
    0,
  );
}

/**
 * @description 计算椭圆的短轴长度根据椭圆的中心点长轴长度和椭圆上的一个点的坐标
 * @param center 椭圆的中心点坐标
 * @param longAxisLength 椭圆的长轴长度
 * @param point 椭圆上的一个点的坐标
 * @param angle 椭圆的长轴与x轴的夹角
 * * @returns 短轴长度
 * */
export function calculateShortAxisLength(
  center: Vector3,
  longAxisLength: number,
  point: Vector3,
  angle: number,
): number {
  // # 将角度转换为弧度# 坐标变换
  const { x: x1, y: y1 } = point;
  const { x: h, y: k } = center;
  const x_prime = (x1 - h) * Math.cos(angle) + (y1 - k) * Math.sin(angle);
  const y_prime = -(x1 - h) * Math.sin(angle) + (y1 - k) * Math.cos(angle);
  // # 计算短轴长度
  return Math.sqrt(
    (y_prime ** 2 * longAxisLength ** 2) / (longAxisLength ** 2 - x_prime ** 2),
  );
}

/**
 * @description 判断点是否在线段垂直范围内
 * @param point 点的坐标
 * @param lineStart 线段起点坐标
 * @param lineEnd 线段终点坐标
 * * @returns boolean
 * */
export function isPointInVerticalRange(
  point: Vector3,
  lineStart: Vector3,
  lineEnd: Vector3,
) {
  // 点和线段端点的坐标
  const { x: px, y: py } = point;
  const { x: x1, y: y1 } = lineStart;
  const { x: x2, y: y2 } = lineEnd;
  // 计算线段的向量
  const dx = x2 - x1;
  const dy = y2 - y1;
  // 计算点到线段起点的向量
  const pdx = px - x1;
  const pdy = py - y1;
  // 计算点到线段的投影长度
  const projection = (pdx * dx + pdy * dy) / (dx * dx + dy * dy);
  // 判断投影长度是否在0和1之间，即点是否在线段上
  return projection > 0.01 && projection < 0.99;
}

// 判断两个向量是否平行
export function isParallel(vec1: Vector, vec2: Vector): boolean {
  vec1 = vec1.normalize();
  vec2 = vec2.normalize();
  if (isEquals(vec1, vec2) || isEquals(vec1.clone().negate(), vec2)) {
    return true;
  } else {
    return false;
  }
}

export const sketchPreview = (curves: ICurve[]) => {
  const frame = sketchEventCenter().getSketchInfo().frame;
  const sketchFrame = new SketchFrame(frame);
  const previewScene = new Object3D();
  for (let i = 0; i < curves.length; i++) {
    drawCurvePreview(curves[i], previewScene, sketchFrame);
  }
  previewer.preview([previewScene]);
};
const drawCurvePreview = (
  curve: ICurve,
  previewScene: Object3D,
  sketchFrame: SketchFrame,
) => {
  const positions = [];
  // 画曲线
  for (let i = 0; i < curve.vertices.length; i++) {
    const pos = sketchFrame.toWorld(
      new Vector3(curve.vertices[i][0], curve.vertices[i][1], 0),
    );
    positions.push(new Vector3(pos.x, pos.y, pos.z));
  }
  if (positions.length === 1) {
    const point = new CSPoint({
      vertex: positions[0],
      pointType: 'midPoint',
    });
    point.hoverOn();
    point.setSize(6);
    point.renderOrder = 99;
    previewScene.add(point);
    ctx2.cadScene.assisGroup.add(point);
    ctx2.viewer.requestRender();
  } else {
    const line = new CSLine({
      vertexs: positions,
      lineWidth: 4,
    });
    line.setColor('#fed200');
    line.renderOrder = 99;
    previewScene.add(line);
    ctx2.cadScene.assisGroup.add(line);
    ctx2.viewer.requestRender();
  }
};

// 草图计算两点之间线段的长度
export const calcLineLength = (pointA: Vector2, pointB: Vector2) => {
  const vecA = new Vector2(pointA.x, pointA.y);
  const vecB = new Vector2(pointB.x, pointB.y);
  const distance = vecA.distanceTo(vecB);
  return distance;
};
// 计算两个向量夹角
export const calcSketcherAngel = (vecA: Vector2, vecB: Vector2) => {
  const dotProduct = vecA.dot(vecB);
  const magnitudeA = vecA.length();
  const magnitudeB = vecB.length();
  const cosAngle = dotProduct / (magnitudeA * magnitudeB);
  const angle = Math.acos(cosAngle) * (180 / Math.PI);
  return angle;
};

// 两个相交直线的于圆的两个切点，求圆心
/**
 * @param T1 线1是一条边的切点
 * @param T2 线2的另一条边的切点
 * @param P 线1和线2的交点
 * */
export function calcArcCircleCenter(T1: Vector2, T2: Vector2, P: Vector2) {
  // 计算切线方向向量
  const tangent1 = new Vector2().subVectors(T1, P);
  const tangent2 = new Vector2().subVectors(T2, P);

  // 计算法线方向向量
  const normal1 = new Vector2().set(-tangent1.y, tangent1.x).normalize();
  const normal2 = new Vector2().set(-tangent2.y, tangent2.x).normalize();
  // 计算法线方向上偏移距离为10的点，因为交点需要知道两个点
  const distance = 100;
  const poitnOnLine1 = new Vector2().addVectors(
    T1,
    new Vector2().copy(normal1).multiplyScalar(distance),
  );
  const poitnOnLine2 = new Vector2().addVectors(
    T2,
    new Vector2().copy(normal2).multiplyScalar(distance),
  );
  const circleCenter = getLineIntersectionTwo(
    T1,
    poitnOnLine1,
    T2,
    poitnOnLine2,
  );

  const prevCircleRadius = calcLineLength(circleCenter, T1);

  const circleRadius =
    prevCircleRadius < 1
      ? Number(prevCircleRadius.toFixed(2))
      : Math.floor(prevCircleRadius);

  return { circleCenter, circleRadius };
}

export const addDimension = async (
  sketchId: string,
  userData: DimInfo,
  isExist = false,
) => {
  const { value, type, objects } = userData;
  const types = [
    AnnotationType.DimensionAnno,
    AnnotationType.DistanceXAnno,
    AnnotationType.DistanceYAnno,
  ];

  const result = { ...userData };

  // 把线的id 变成两个点的id
  if (types.includes(type) && objects.length === 1) {
    const curve = dimRender().getSketMap().get(result.objects[0]);
    result.objects = [curve.relative[0], curve.relative[1]];
    if (type === AnnotationType.DimensionAnno) {
      result.type = AnnotationType.DistanceAnno;
    }
    // 如果第二个点是ORIGIN，则把ORIGIN放到第一个
    if (result.objects[1] === 'ORIGIN') {
      result.objects = [result.objects[1], result.objects[0]];
      // 对参数进行调整
      if (result.type !== AnnotationType.DistanceAnno) {
        result.value = -result.value;
      } else {
        result.points = [
          [-result.points[0][0], -result.points[0][1]],
          result.points[1],
        ];
      }
    }
  }
  const res = await wsApi.addDimension(sketchId, {
    curve: result.objects,
    type: result.type,
    value: result.param || result.value,
    points: result.points,
  });
  renderSketch(sketchId, { changed: res.changed });
  dimRender().renderDim({
    ...result,
    id: res.added[0],
    master: !isExist,
    value,
  });
};
export const isEffectiveValue = (num: number | Vector3 | Vector2) => {
  if (num instanceof Vector3 || num instanceof Vector2) {
    return (
      Number.isFinite(num.x) &&
      Number.isFinite(num.y) &&
      !isNaN(num.x) &&
      !isNaN(num.y) &&
      num.x != null &&
      num.y != null
    );
  } else {
    return Number.isFinite(num) && !isNaN(num) && num != null;
  }
};

let _previousCameraStatus: {
  up: Vector3;
  normal: Vector3;
};

export const dashOptions = { dashSize: 8, gapSize: 4 };
export const lineWidthOptions = { dashed: 1, soild: 3 };
// 进入草图
export const enterSketch = async ({
  sketchId,
  frame: rawFrame,
  skipCameraChange = false,
}: {
  sketchId: string;
  frame: Vector3Tuple[];
  skipCameraChange?: boolean;
}) => {
  // frame需要先normalize
  const frame = [
    rawFrame[0],
    new Vector3(...rawFrame[1]).normalize().toArray(),
    new Vector3(...rawFrame[2]).normalize().toArray(),
  ];
  if (sketchId && frame && !eventCenterManager.isSketchMode) {
    // 清除core缓存
    wsApi.clearVisualData(sketchId);

    userEvents.emit(CONSTRUCTION_LINE, { flag: true });
    const picker3d = PickerMgr.getPicker<Picker3D>('3d');
    const renderer3d = RendererMgr.getRender<Renderer3D>('3d');

    // 清理状态
    cmdInvoker.resetCmd();
    picker3d.clickNull();

    // 设置各控制器状态
    eventCenterManager.switchTo(sketchEventCenter().id);
    sketchEventCenter().setSketchInfo({ sketchId, frame });
    sketchEventCenter().setLineType(CurveStyle.SolidLine);
    docMgr.setCurrentSketchDxid(sketchId);

    userEvents.emit(SKETCH_EVENT.SKETCH_EVENT_CHANGE, { type: 'sketchIn' });
    // 特征树双击后，commandTab,切换到草图Bar
    userEvents.emit(CMEVENTS.CMD_TABS_ACTIVE, 'sketch');

    // 隐藏3d渲染
    renderer3d.setModelVisible(docMgr.getCurrentSketchDxid(), false);

    // guideRenderer().init();

    const picker = PickerMgr.getPicker<Picker2D>('2d');
    const renderer = RendererMgr.getRender<Renderer2D>('2d');
    renderer.dashOptions = dashOptions;
    renderer.lineWidthOptions = lineWidthOptions;

    renderer.setPlacement(frame);
    constraintIconRenderer.frame = frame;
    await ConstraintIcon.initResource();
    await resetRenderSketch(sketchId);
    ctx2.viewer.sceneSetup.setWorkGroup(ctx2.cadScene.sketchGroup);

    const camera = ctx2.viewer.sceneSetup.camera as OrthographicCamera;
    // 正视于草图
    if (!skipCameraChange) {
      _previousCameraStatus = {
        up: camera.up.clone(),
        normal: ctx2.viewer.sceneSetup.getCameraNormal().negate(),
      };

      const { up, normal } = getNearFaceNormalAndUp(frame);
      ctx2.viewer.sceneSetup.rotateView(normal, up);
    } else {
      _previousCameraStatus = null;
    }

    withFeature('sketch_grids', () => {
      if (!docMgr.getCurrentSketchDxid()) {
        // in case sketcher exited immediately after entered
        return;
      }

      // 设置并显示网格
      const origin = new Vector3(...frame[0]);
      const axisZ = new Vector3(...frame[1]);
      const axisX = new Vector3(...frame[2]);
      sketch_grids.setPositionFromAxis(origin, axisX, axisZ);
      sketch_grids.show();
    })();

    cmdMgr.updateUndoSize();
    picker.clickNull();
  }

  if (sketchId) {
    const modelsData = await CoreBasicCommands.getVisualData(
      [sketchId],
      ExtensionID.CAD,
      false,
      true,
    );
    ctx2.viewer.sceneSetup.adjustCameraForVisualData(modelsData);
  }
};

export const exitSketcherMethod = async (sketchId: string) => {
  const oldID = docMgr.getCurrentSketchDxid();
  if (oldID == null) {
    return;
  }

  docMgr.setCurrentSketchDxid(undefined); // unset current sketch id immediately
  // to avoid unnecessary loading of sketch data while updateDocumentObjects

  const targetObj = docMgr.getDocumentObjectById(sketchId);

  if (targetObj != null) {
    await wsApi.exitSketcher(sketchId);
  }

  eventCenterManager.switchTo(defaultEventCenter().id);
  constraintIconRenderer.clear();
  dimRender().remove();

  RendererMgr.getRender<Renderer2D>('2d').clear();
  userEvents.emit(SKETCH_EVENT.SKETCH_EVENT_CHANGE, {
    type: 'sketchOut',
    oldID,
  });

  const renderer3d = RendererMgr.getRender<Renderer3D>('3d');

  const oldObj = docMgr.getDocumentObjectById(oldID);
  if (oldObj != null) {
    renderer3d.setModelVisible(oldID, oldObj.isRecursiveVisible);
  }

  ctx2.viewer.sceneSetup.setWorkGroup(null);

  if (_previousCameraStatus != null) {
    ctx2.viewer.sceneSetup.rotateView(
      _previousCameraStatus.normal,
      _previousCameraStatus.up,
    );

    _previousCameraStatus = null;
  }

  withFeature('sketch_grids', () => {
    // 隐藏网格
    sketch_grids.hide();
  })();
};

/*** @deprecated*/
export const faceToSketchGroup = (frame: Vector3Tuple[]) => {
  const origin = new Vector3(...frame[0]);
  const xDir = new Vector3(...frame[2]);
  const zDir = new Vector3(...frame[1]);
  const yDir = new Vector3().crossVectors(zDir, xDir);
  const up = yDir.toArray();
  const look = frame[0];
  const target = frame[0];
  const camera = ctx2.viewer.sceneSetup.camera;
  const { distance, zoom } = computeCameraParams(
    ctx2.cadScene.sketchGroup,
    ctx2.cadScene.workGroup,
    camera,
    ctx2.viewer.sceneSetup.container,
  );
  // const factor = 0.0001;
  // camera.zoom = zoom * distance * factor;
  const pos = origin.clone().add(zDir.setLength(distance * zoom));
  ctx2.viewer.sceneSetup.setCameraLookAt(pos.toArray(), up, look, target);
};

const computeCameraParams = (
  sketchGroup: Object3D,
  workGroup: Object3D,
  camera: PerspectiveCamera | OrthographicCamera,
  container: HTMLElement,
  minRadius = 250,
) => {
  const angle2Radian = (a: number) => {
    return (a * Math.PI) / 180;
  };
  const box = new Box3();
  const target = new Sphere();

  box.setFromObject(sketchGroup);
  box.getBoundingSphere(target);
  const sketchDiameter = Math.max(target.radius, minRadius) * 2;

  box.setFromObject(workGroup);
  box.getBoundingSphere(target);
  const diameter = Math.max(target.radius, minRadius) * 2;

  let distance = 0,
    zoom = 0;
  if (camera instanceof PerspectiveCamera) {
    const k = camera.aspect;
    let angle = angle2Radian(camera.fov / 2);
    if (k < 1) {
      angle = Math.atan(Math.tan(angle2Radian(camera.fov / 2)) * k);
    }
    distance = (0.8 * target.radius) / Math.tan(angle);
  } else {
    const clientSize = Math.max(container.clientWidth, container.clientHeight);
    // zoom = diameter / (clientSize * 0.8); // 为什么是乘以 0.8，我也不知道
    zoom = clientSize / (diameter * 0.8);
    distance = diameter;
  }

  // 工作区比草图大，放大
  if (diameter > sketchDiameter) {
    zoom = zoom * (diameter / sketchDiameter);
  }
  return {
    distance,
    zoom,
  };
};
// 编辑线
export const editLine = async (params, type) => {
  const sketchId = sketchEventCenter().getSketchInfo()?.sketchId;
  const modifyData: ReturnDataInfo = await wsApi.editSketchCurve(
    sketchId,
    type,
    params,
  );
  //TODO 更新线的宽度， 等skd render 中 updateCurves 中添加更具实线虚线修改线的宽度的操作后删除
  const oldCurve: any = ctx2.cadScene.sketchGroup.children.find(
    (child) => getObjectInfo<ICurve>(child)?.id === params.curve,
  );
  oldCurve.setLineWidth(
    params.curveStyle === 0 ? lineWidthOptions.soild : lineWidthOptions.dashed,
  );
  // -----------------------
  renderSketch(sketchId, { changed: [...modifyData.changed, params.curve] });
};

// 选中面以创建草图时，相同的法向量对应相同的坐标系
export const getFaceFrame = async (pickedObject: pickedFaceData) => {
  let planePoint: Vector3Tuple = [0, 0, 0];
  let planeNormal: Vector3Tuple;

  const { meshId } = pickedObject;

  // 基础面
  if (meshId === 'XOY') {
    planeNormal = [0, 0, 1];
  } else if (meshId === 'YOZ') {
    planeNormal = [1, 0, 0];
  } else if (meshId === 'XOZ') {
    planeNormal = [0, 1, 0];
  } else if (['YOZ', 'XOZ', 'XOY'].some((id) => meshId.endsWith(`:${id}`))) {
    // 坐标系的面
    const dxid = meshId.split(':')[0];

    const documentObject = docMgr.getDocumentObjectById(dxid);
    const csysFrame = documentObject.params.frame;

    const axisIndex = ['YOZ', 'XOZ', 'XOY'].findIndex((id) =>
      meshId.endsWith(`:${id}`),
    );

    planePoint = csysFrame[0];
    if (axisIndex === 0) {
      planeNormal = csysFrame[2];
    } else if (axisIndex === 1) {
      planeNormal = new Vector3(...csysFrame[1])
        .cross(new Vector3(...csysFrame[2]))
        .normalize()
        .toArray();
    } else if (axisIndex === 2) {
      planeNormal = csysFrame[1];
    }
  } else {
    // 实体面
    // getFaceFrame需要:fx
    const meshID = pickedObject.meshId.includes(':')
      ? pickedObject.meshId
      : `${pickedObject.meshId}:f0`;
    const result = await wsApi.getFaceFrame(meshID);
    planePoint = result[0];
    planeNormal = result[1];
  }

  let frameX: Vector3Tuple;

  const axes: Vector3Tuple[] = [
    [1, 0, 0],
    [0, 1, 0],
    [0, 0, 1],
  ];

  const parallelAxisIndex = axes.findIndex(
    ([ax, ay, az]) =>
      ax * planeNormal[0] + ay * planeNormal[1] + az * planeNormal[2] === 0,
  );

  if (planeNormal.filter((v) => Math.abs(v) < minNum).length === 2) {
    // 若平面和某个基础平面平行，则取该平面上的“x”为u
    frameX =
      axes[(planeNormal.findIndex((v) => Math.abs(v) >= minNum) + 2) % 3];
  } else if (parallelAxisIndex !== -1) {
    // 若平面不与基础平面平行，但与某个基础轴平行，则取该轴正向为u
    frameX = axes[parallelAxisIndex];
  } else {
    // 都不是，则取平面与XOY平面的交线为X轴方向
    const xBasis = new Vector3(0, 0, 1)
      .cross(new Vector3(...planeNormal))
      .normalize();

    if (xBasis.x < 0) {
      xBasis.negate();
    }

    frameX = xBasis.toArray();
  }

  const origin = new Vector3(...planeNormal).multiplyScalar(
    new Vector3(...planePoint).dot(new Vector3(...planeNormal).normalize()),
  );

  return [origin.toArray(), planeNormal, frameX] as Tuple3<Vector3Tuple>;
};

export const deleteSketchCruve = async () => {
  const sketchId = sketchEventCenter().getSketchInfo().sketchId;
  const selectedModels =
    PickerMgr.getPicker<Picker2D>('2d').getSelectedObjectInfo();
  const curvedId = [];
  let isSelect = false;
  // 删除物体
  if (selectedModels && selectedModels.length > 0) {
    selectedModels.forEach((item) => {
      curvedId.push(item.id);
    });
    isSelect = true;
    // 删除约束图标

    const res = await wsApi.deleteSketchCurve(sketchId, curvedId);

    sketchEventCenter().clearSelectedModels();

    await renderSketch(sketchId, res);
    ctx2.cadScene.assisGroup.clear();
    SceneGraph.emptyGroup(ctx2.cadScene.assisGroup);
  }
  // 删除标注
  const selectedDim = dimPicker().getSelectedObj();
  const dimIds = [];
  selectedDim.forEach((item) => {
    if (
      !curvedId.includes(item.objects[0]) &&
      !curvedId.includes(item.objects[1])
    ) {
      dimIds.push(item.id);
    }
  });
  if (selectedDim && selectedDim.length > 0) {
    isSelect = true;
    const res = await wsApi.deleteDimension(sketchId, { annotations: dimIds });
    dimRender().removeByDimIds(dimIds);
    dimRender().setUndisable(res.changed);
  }

  if (constraintIconPicker.selectedIcons?.length > 0) {
    isSelect = true;
    const constraint =
      constraintIconPicker.selectedIcons[0].constraintTag.constraint;

    await wsApi.deleteSketchConstraint(
      docMgr.getCurrentSketchDxid(),
      constraint.itemid,
      constraint.target,
      constraint.type,
    );

    constraintIconRenderer.removeConstraint(constraint, true);
    docMgr.loadSketchChildren(docMgr.getCurrentSketchDxid());
    constraintIconPicker.clearSelect();
  }

  if (!isSelect) {
    MessageTip.error('未选中物体');
  }
};

export const basicSketchIds = ['ORIGIN', 'X-AXIS', 'Y-AXIS'];
